﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Collections;
using FacebookWrapper.ObjectModel;

namespace B12_Ex03_Eran_053094363_Assaf_036722999
{
    public partial class FriendsForm : Form, ISearchSubject
    {
        private readonly List<Image> m_FacebookImagesList = new List<Image>();

        private readonly Dictionary<string, int> m_DicTreeNodes = new Dictionary<string, int>();

        private FacebookManager m_FacebookManager = FacebookManager.Instance;

        private object m_ListObjectLocker = new object();

        private int m_FinishedThreads;

        private List<User> m_FacebookFriends;

        private List<Post> m_FriendsPost;

        List<IObserver> m_Observers = new List<IObserver>();

        public FriendsForm()
        {
            InitializeComponent();
            Attach(new SearchingLabel());
        }

        private void treeFilterFriends_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            try
            {
                if (treeFilterFriends.SelectedNode != null)
                {
                    FriendDetails frmDetails = new FriendDetails();
                    User foundUser = m_FacebookFriends.Find(
                        delegate(User fbUser)
                        {
                            return fbUser.Name == treeFilterFriends.SelectedNode.Text;
                        });

                    if (foundUser != null)
                    {
                        frmDetails.ShowDetails(foundUser);
                    }
                }
            }
            catch (Exception)
            {
                // No user found or Facebook User Collection is not initialized.
            }
        }

        private void filterByGender()
        {
            // Clear the Nodes Lists
            m_FacebookImagesList.Clear();
            m_DicTreeNodes.Clear();

            // Create Tree Root Nodes and Attach Proper Images
            ImageList usersImageList = new ImageList();
            usersImageList.Images.Add(Properties.Resources.Male_symbol_normal);
            usersImageList.Images.Add(Properties.Resources.Female_symbol_normal);
            TreeNode maleNode = treeFilterFriends.Nodes.Add("Male");
            maleNode.ImageIndex = 0;
            maleNode.SelectedImageIndex = 0;
            TreeNode femaleNode = treeFilterFriends.Nodes.Add("Female");
            femaleNode.ImageIndex = 1;
            femaleNode.SelectedImageIndex = 1;
            TreeNode elseNode = treeFilterFriends.Nodes.Add("Homosapian");
            elseNode.ImageIndex = -1;

            // Create the Requested Quires for the Root Nodes
            var allMales = (from male in m_FacebookFriends
                            where male.Gender == User.eGender.male
                            select male);
            var allFemales = (from female in m_FacebookFriends
                              where female.Gender == User.eGender.female
                              select female);
            var allTheRest = (from other in m_FacebookFriends
                              where other.Gender != User.eGender.male && other.Gender != User.eGender.female
                              select other);

            // Set the Number of Created Threads
            m_FinishedThreads = m_FacebookFriends.Count;

            // Set the Image List to the Tree
            treeFilterFriends.ImageList = usersImageList;

            // Add the Friends to the Root Tree Nodes using Threads
            Thread[] friendsThreads = new Thread[3];
            friendsThreads[0] = new Thread(() => { treeFilterFriends.Invoke(new Action(() => { addChildNodes(maleNode, allMales); })); }); // Male Tree Node
            friendsThreads[1] = new Thread(() => { treeFilterFriends.Invoke(new Action(() => { addChildNodes(femaleNode, allFemales); })); }); // Female Tree Node
            friendsThreads[2] = new Thread(() => { treeFilterFriends.Invoke(new Action(() => { addChildNodes(elseNode, allTheRest); })); }); // Others Tree Node

            // Start the Threads
            int i = 0;
            for (i = 0; i < friendsThreads.Count(); i++)
            {
                friendsThreads[i].Start();
            }

            // Join the Threads
            for (i = 0; i < friendsThreads.Count(); i++)
            {
                friendsThreads[i].Join(250);
            }

            // Check that all threads are done and update the images
            new Thread(() =>
            {
                while (m_FinishedThreads != 0)
                {
                    Thread.Sleep(200);
                }

                // Add the Images to the Image List
                treeFilterFriends.Invoke(new Action(() =>
                {
                    usersImageList.Images.AddRange(m_FacebookImagesList.ToArray());
                    updateTreeNodesImages();
                }));
            }).Start();


            // Update the Counters on the Tree Root Nodes
            maleNode.Text += string.Format(" ({0})", allMales.Count());
            femaleNode.Text += string.Format(" ({0})", allFemales.Count());
            elseNode.Text += string.Format(" ({0})", allTheRest.Count());
        }

        private void addChildNodes(TreeNode i_RootTreeNode, IEnumerable i_Collection)
        {
            foreach (User item in i_Collection)
            {
                // Create a New Node
                TreeNode addedNode = i_RootTreeNode.Nodes.Add(item.Name);

                // Set the Friend Details
                addedNode.Name = addedNode.Text.Replace(' ', '_');
                FriendWrapperForThreading wrapper = new FriendWrapperForThreading();
                wrapper.FriendPictureURL = item.PictureSmallURL;
                wrapper.FriendTreeItem = addedNode.Name;
                wrapper.Finished += new EventHandler(wrapper_Finished);
                new Thread(new ThreadStart(wrapper.GetFriendImage)).Start();
            }
        }

        private void wrapper_Finished(object sender, EventArgs e)
        {
            // Add the Image Index
            FriendWrapperForThreading wrapper = (FriendWrapperForThreading)sender;
            lock (m_ListObjectLocker)
            {
                int imageIndex = m_FacebookImagesList.Count;
                m_FacebookImagesList.Add(wrapper.FriendImage);
                setImageIndex(wrapper, imageIndex + 2);

                // Decrease the Number of finished Threads
                m_FinishedThreads--;
            }
        }

        private void setImageIndex(FriendWrapperForThreading i_FriendWrapper, int i_ImageIndex)
        {
            // Lock the Access to the Tree Node Item from other Threads
            lock (m_ListObjectLocker)
            {
                m_DicTreeNodes.Add(i_FriendWrapper.FriendTreeItem, i_ImageIndex);
            }
        }

        private void updateTreeNodesImages()
        {
            foreach (string key in m_DicTreeNodes.Keys)
            {
                try
                {
                    TreeNode node = treeFilterFriends.Nodes.Find(key, true)[0];
                    if (node != null)
                    {
                        node.ImageIndex = node.SelectedImageIndex = m_DicTreeNodes[key];
                    }
                }
                catch (Exception)
                {
                    // Might not find the Node
                }
            }
        }

        private void filterPostsSearch()
        {
            // Get the Search Parameters
            string selectedPostType =
                comboBoxPostType.SelectedItem != null ?
                comboBoxPostType.SelectedItem.ToString() : string.Empty;

            string filterByText = textBoxKeyword.Text;

            try
            {
                // Create a generic Collectio of posts, selecting only the number of required posts
                var postCollection = (from post in m_FriendsPost
                                      orderby post.UpdateTime descending
                                      select post);

                // Check the Searh Terms
                if (!string.IsNullOrEmpty(selectedPostType))
                {
                    var postByTypeCollection = (from post in postCollection
                                                where isTypeOf(post, selectedPostType)
                                                orderby post.UpdateTime descending
                                                select post);

                    postCollection = postByTypeCollection;
                }

                if (!string.IsNullOrEmpty(filterByText))
                {
                    var postByTextCollection = (from post in postCollection
                                                where hasText(post, filterByText)
                                                orderby post.UpdateTime descending
                                                select post);

                    postCollection = postByTextCollection;
                }

                new Thread(() =>
                {
                    // Add the Posts to the Tree
                    treeFilterFriends.Invoke(new Action(() =>
                    {
                        foreach (var item in postCollection)
                        {
                            addToTree(item);
                        }
                    }));
                }).Start();
            }
            catch (Exception)
            {
                // Some Types are not recognized
            }
        }

        private bool isTypeOf(Post i_Post, string i_PostType)
        {
            bool retResult = false;

            try
            {
                if (i_Post.Type != null)
                    if (i_Post.Type.HasValue)
                        if (i_Post.Type.ToString().Equals(i_PostType, StringComparison.InvariantCultureIgnoreCase))
                            retResult = true;
            }
            catch (Exception)
            { }

            return retResult;
        }

        private bool hasText(Post i_Post, string i_TextFilter)
        {
            // Don't Match by Default
            bool retContainsText = false;
            string textToCheck = getText(i_Post);

            // Check the Text Filter
            if (textToCheck.Contains(i_TextFilter))
            {
                retContainsText = true;
            }

            return retContainsText;
        }

        private string getText(Post i_Post)
        {
            string retText = string.Empty;

            if (i_Post.Caption != null)
            {
                retText = i_Post.Caption;
            }
            else if (i_Post.Comments != null)
            {
                foreach (var item in i_Post.Comments)
                {
                    if (item.Message != null)
                    {
                        retText += item.Message;
                    }
                }
            }
            else if (i_Post.Description != null)
            {
                retText = i_Post.Description;
            }
            else if (i_Post.Link != null)
            {
                retText = i_Post.Link;
            }
            else if (i_Post.Message != null)
            {
                retText = i_Post.Message;
            }
            else if (i_Post.Name != null)
            {
                retText = i_Post.Name;
            }
            else if (i_Post.Source != null)
            {
                retText = string.Format("{ Source: {0} }", i_Post.Source);
            }
            else if (i_Post.Type.HasValue)
            {
                retText = string.Format("{ Type: {0} }", i_Post.Type.Value.ToString());
            }

            return retText;
        }

        private void addToTree(Post i_Post)
        {
            TreeNode postCreatorNode = null;

            TreeNode[] foundNodes = treeFilterFriends.Nodes.Find(i_Post.From.Name, false);
            if (foundNodes.Length == 0)
            {
                postCreatorNode = treeFilterFriends.Nodes.Add(i_Post.From.Name);
                postCreatorNode.Name = i_Post.From.Name;
            }
            else
            {
                postCreatorNode = foundNodes[0];
            }

            postCreatorNode.Nodes.Add(getPostTextFormat(i_Post));
        }

        private string getPostTextFormat(Post i_Post)
        {
            string retTextFormat = string.Format(
                "[{0}] {1}",
                i_Post.CreatedTime,
                getText(i_Post));

            return retTextFormat;
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            // Notify the Search Observers that we are starting a Search
            Notify();

            // Clear the Tree View
            treeFilterFriends.Nodes.Clear();
            treeFilterFriends.ImageList = null;

            // Disable the Button until functions are done
            btnSearch.Enabled = false;

            if (radioGender.Checked)
            {
                // Set the Friends List By Choise
                switch (comboBoxFriendsFilter.SelectedIndex)
                {
                    case 0:
                        m_FacebookFriends = m_FacebookManager.GetFriends();
                        break;
                    case 1:
                        m_FacebookFriends = m_FacebookManager.GetFriendsOfFriends();
                        break;
                    default:
                        break;
                }

                // Filter By Gender by invoking the command to avoid holding the GUI
                new Thread(new ThreadStart(() => treeFilterFriends.Invoke(new Action(() => filterByGender())))).Start();
            }
            else if (radioPost.Checked)
            {
                // Get the Posts Count
                int postsCount;
                int.TryParse(comboBoxMaxPosts.SelectedItem.ToString(), out postsCount);

                // Set the Post List
                m_FriendsPost = m_FacebookManager.GetFriendsPost(postsCount);

                // Filter and Search by Post by invoking the command to avoid holding the GUI
                new Thread(() =>
                {
                    treeFilterFriends.Invoke(new Action(() => filterPostsSearch()));
                }).Start();
            }

            // All Done - Notify the Search Observers that we are Done 
            Notify();

            // Enable the Button
            btnSearch.Enabled = true;
        }

        private void comboBoxPostType_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (m_FriendsPost != null)
            {
                filterPostsSearch();
            }
        }

        private void comboBoxMaxPosts_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (m_FriendsPost != null)
            {
                filterPostsSearch();
            }
        }

        private void radioGender_CheckedChanged(object sender, EventArgs e)
        {
            bool enable = false;

            if (radioGender.Checked)
            {
                enable = true;
            }

            comboBoxFriendsFilter.Enabled = enable;
        }

        private void radioPost_CheckedChanged(object sender, EventArgs e)
        {
            bool enable = false;

            if (radioPost.Checked)
            {
                enable = true;
            }

            comboBoxMaxPosts.Enabled = enable;
            comboBoxPostType.Enabled = enable;
            textBoxKeyword.Enabled = enable;
        }

        private void btnBack_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            treeFilterFriends.Nodes.Clear();
        }

        public void Notify()
        {
            foreach (var observer in m_Observers)
            {
                observer.Update(this);
            }
        }

        public void Attach(IObserver i_Observer)
        {
            i_Observer.Add(this);
            m_Observers.Add(i_Observer);
        }

        public void Detach(IObserver i_Observer)
        {
            m_Observers.Remove(i_Observer);
        }
    }
}
